<?php

namespace Log;

/**
 * Class TraceLog
 *
 * PHP Version > 7.0
 *
 * @category  Libraries
 * @package   PHP-Log
 * @author    Jon
 * @doc       write log
 * @link      https://gitee.com/joncc/trace-log
 */
class TraceLog
{
    const EMERGENCY = 0;

    const ALERT = 1;

    const CRITICAL = 2;

    const ERROR = 3;

    const WARNING = 4;

    const NOTICE = 5;

    const INFO = 6;

    const DEBUG = 7;

    const ALL = 8;

    /**
     * @var array
     */
    private static $levels = [
        'EMERGENCY' => self::EMERGENCY,
        'ALERT' => self::ALERT,
        'CRITICAL' => self::CRITICAL,
        'ERROR' => self::ERROR,
        'WARNING' => self::WARNING,
        'NOTICE' => self::NOTICE,
        'INFO' => self::INFO,
        'DEBUG' => self::DEBUG,
        'ALL' => self::ALL,
    ];

    /**
     * @var string
     */
    private static $dir;

    /**
     * @var string
     */
    private static $level;

    /**
     * @var int
     */
    private static $level_num = 6;

    /**
     * @var string
     */
    private static $separator = ' | ';

    /**
     * @var string
     */
    private static $home_directory = 'trace';

    /**
     * @var int
     */
    private static $file_size = 10485760; //10M
    /**
     * @var string
     */
    private static $file_prefix = '';

    /**
     * @var string
     */
    private static $file_suffix = 'log';


    /**
     * @author Jon
     * @param string $dir
     */
    public static function setDir(string $dir): void
    {
        self::$dir = $dir;
    }

    /**
     * @author Jon
     */
    public static function isDebug(): void
    {
        self::$level_num = self::DEBUG;
    }

    /**
     * @author Jon
     * @param int $level
     */
    public static function setLevel(int $level): void
    {
        self::$level_num = $level;
    }

    /**
     * @author Jon
     * @param string $directory
     */
    public static function setHomeDirectory(string $directory): void
    {
        self::$home_directory = $directory;
    }

    /**
     * @author Jon
     * @param string $prefix
     */
    public static function setFilePrefix(string $prefix): void
    {
        self::$file_prefix = $prefix;
    }

    /**
     * @author Jon
     * @param string $suffix
     */
    public static function setFileSuffix(string $suffix): void
    {
        self::$file_suffix = $suffix;
    }

    /**
     * @author Jon
     * @param int $size
     */
    public static function setFileSize(int $size): void
    {
        self::$file_size = $size;
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function emergency(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function alert(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function critical(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function error(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function warning(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function notice(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function info(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function debug(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param mixed ...$msg
     * @return bool
     */
    public static function all(...$msg): bool
    {
        if (!self::getLevel(__FUNCTION__)) {
            return false;
        }

        return self::log(self::parseTrace(debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 2)));
    }

    /**
     * @author Jon
     * @param string $date
     * @return bool
     */
    public static function clear(string $date = ''): bool
    {
        if (empty($date)) {
            return false;
        }

        $dir = self::getDir();
        $list = scandir($dir);

        foreach ($list as $file_name) {
            if (in_array($file_name, ['.', '..'])) {
                continue;
            }

            $regulation = '/' . self::$file_prefix . '[\.]{0,1}' . $date . '[\s\S]{0,20}\.' . self::$file_suffix . '/';
            preg_match($regulation, $file_name, $file_name_arr);
            if (!isset($file_name_arr[0])) {
                continue;
            }

            $name = $file_name_arr[0];
            unlink($dir . $name);
        }

        return true;
    }

    /**
     * @author Jon
     * @param array $msg
     * @return bool
     */
    private static function log(array $msg): bool
    {
        $destination = self::getDir() . self::getFileName(date('Ymd'));
        self::checkLogSize($destination);

        return error_log(implode(self::$separator, $msg) . PHP_EOL, 3, $destination);
    }

    /**
     * @author Jon
     * @param string $destination
     */
    private static function checkLogSize(string $destination): void
    {
        if (is_file($destination) && floor(self::$file_size) <= filesize($destination)) {
            try {
                $file_name = basename($destination);
                $name_arr = explode('.', $file_name);
                $name = self::getFileName($name_arr[0] . '.' . date('His'));

                rename($destination, dirname($destination) . DIRECTORY_SEPARATOR . $name);
            } catch (\Exception $e) {
            }
        }
    }

    /**
     * @author Jon
     * @param string $level
     * @return string
     */
    private static function getLevel(string $level): string
    {
        $upper_level = strtoupper($level);
        if (self::$levels[$upper_level] > self::$level_num) {
            return false;
        }

        self::$level = $upper_level;
        return $upper_level;
    }

    /**
     * @author Jon
     * @return string
     */
    private static function getDir(): string
    {
        if (empty(self::$dir)) {
            self::$dir = sys_get_temp_dir();
        }

        $dir = self::$dir . DIRECTORY_SEPARATOR . self::$home_directory . DIRECTORY_SEPARATOR;
        !is_dir($dir) && mkdir($dir, 0755, true);

        return $dir;
    }

    /**
     * @author Jon
     * @param string $date #20200716
     * @return string
     */
    private static function getFileName(string $date): string
    {
        if (empty(self::$file_prefix)) {
            $file_name_arr = [$date, self::$level, self::$file_suffix];
        } else {
            $file_name_arr = [self::$file_prefix, $date, self::$level, self::$file_suffix];
        }

        return implode('.', $file_name_arr);
    }

    /**
     * @author Jon
     * @param array $trace_info
     * @return array
     */
    private static function parseTrace(array $trace_info): array
    {
        if (!isset($trace_info[0]) || !isset($trace_info[1])) {
            return false;
        }

        return [
            date('Y-m-d H:i:s'),
            self::$level,
            $trace_info[0]['file'],
            $trace_info[1]['function'],
            $trace_info[0]['line'],
            json_encode($trace_info[0]['args'], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_NUMERIC_CHECK),
        ];
    }
}